package org.example.util;


import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.json.JsonMapper;

import javax.websocket.DeploymentException;
import java.io.IOException;
import java.math.BigDecimal;
import java.net.URISyntaxException;
import java.util.*;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.logging.Logger;

public class BtcUtils {
    private static final Logger log = Logger.getLogger(BtcUtils.class.getName());

    private static final String API_URL = "https://contract.mexc.com/api/v1/contract/index_price/BTC_USDT";

    private static final String API_WEBSOCKET = "wss://contract.mexc.com/ws";

    private static BtcClient btcClient;

    enum PriceBase {
        USD, CNY
    }

    public static class BtcPrice{
        private final BigDecimal price;
        private final long time;
        private final PriceBase price_base;

        public BtcPrice(BigDecimal price, long time, PriceBase price_base) {
            this.price = price;
            this.time = time;
            this.price_base = price_base;
        }

        @Override
        public String toString() {
            return "BtcPrice{" +
                    "price=" + price +
                    ", time=" + time +
                    ", price_base=" + price_base +
                    '}';
        }
    }

    public static BtcPrice getPriceInUSD() {

        Map<?, ?> resp = HttpUtils.get(API_URL);
        Map<?, ?> price = (Map<?, ?>) resp.get("data");

        return new BtcPrice(
                BigDecimal.valueOf(Double.parseDouble(String.valueOf(price.get("indexPrice")))),
                (Long) price.get("timestamp"),
                PriceBase.USD
        );
    }

    public static void closeBtcClient() {
        if (btcClient != null) {
            btcClient.close();
            btcClient = null;
        }
    }

    public static BtcClient getBtcClient() {
        if (btcClient == null) {
            try {
                btcClient = new BtcClient();
            } catch (DeploymentException | IOException | URISyntaxException e) {
                e.printStackTrace();
                log.warning("Could not create btc client because: " + e.getLocalizedMessage());
            }
        }
        return btcClient;
    }

    public static class BtcClient{
        private final ReentrantReadWriteLock readWriteLock          = new ReentrantReadWriteLock(false);
        private final ReentrantReadWriteLock.ReadLock  readLock     = readWriteLock.readLock();
        private final ReentrantReadWriteLock.WriteLock  writeLock   = readWriteLock.writeLock();
        private final Timer keepAlive = new Timer();
        private WebSocketUtils.WSClient client;

        private BtcClient() throws DeploymentException, IOException, URISyntaxException {
            this.connect();
        }

        private final ObjectMapper mapper = new JsonMapper();

        private BtcPrice curPrice = null;

        private void onMessage(String msg){
            HashMap<?, ?> resp;
            try {
                resp = mapper.readValue(msg, HashMap.class);
            } catch (JsonProcessingException e) {
                e.printStackTrace();
                return;
            }
            String channel = String.valueOf(resp.get("channel"));
            log.info("Channel " + channel + " arrive.");
            if (channel.equals("pong")) {
                long sub = new Date().getTime() - Long.parseLong(String.valueOf(resp.get("data")));
                log.info("pong in " + sub + "ms");
            } else if(channel.equals("push.ticker")){
                handleChannelTicker(resp);
            } else {
                log.warning("UnHandle channel: " + channel);
            }
        }

        private void handleChannelTicker(Map<?, ?> resp) {
            Map<?, ?> data = (Map<?, ?>) resp.get("data");
            setCurPrice(new BtcPrice(
                    BigDecimal.valueOf(Double.parseDouble(String.valueOf(data.get("indexPrice")))),
                    Long.parseLong(String.valueOf(resp.get("ts"))),
                    PriceBase.USD
            ));
            log.info("Btc price update: " + curPrice.price + "USD " + curPrice.time);
        }

        private void setCurPrice(BtcPrice price) {
            writeLock.lock();
            try {
                this.curPrice = price;
            } finally {
                writeLock.unlock();
            }
        }

        private void ping() {
            send("{ \"method\": \"ping\" }");
        }

        private void ticker() {
            send("{ \"method\":\"sub.ticker\", \"param\":{ \"symbol\":\"BTC_USDT\" } }");
        }

        private void send(String msg) {
            client.sendMessageAsync(msg);
        }

        private void connect() throws DeploymentException, IOException, URISyntaxException {
            client = WebSocketUtils.createClient(API_WEBSOCKET, this::onMessage);
            ping();
            ticker();
            keepAlive.schedule(new TimerTask() {
                @Override
                public void run() {
                    log.info("Execute ping task");
                    ping();
                }
            }, 10000, 10000);
        }

        public BtcPrice getPriceInUSD() {
            readLock.lock();
            try {
                return curPrice;
            } finally {
                readLock.unlock();
            }
        }
        private void close() {
            this.curPrice = null;
            try {
                this.client.close();
            } catch (IOException e) {
                e.printStackTrace();
                log.warning("Could not close btcWsClient because: " + e.getLocalizedMessage());
            }
        }
    }

}
